﻿#include "Dragon.h"
#include "../MirDatabase/MonsterInfo.h"
#include "Map.h"
#include "../MirObjects/Monsters/EvilMir.h"
#include "../Settings.h"
#include "../MirObjects/ItemObject.h"

using namespace Server::MirDatabase;
using namespace Server::MirObjects;
using namespace Server::MirObjects::Monsters;

namespace Server::MirEnvir
{

    Server::MirEnvir::Envir *Dragon::getEnvir()
    {
        return Envir::getMain();
    }

    Server::MessageQueue *Dragon::getMessageQueue()
    {
        return MessageQueue::getInstance();
    }

    Dragon::Dragon(DragonInfo *info)
    {
        Info = info;
    }

    bool Dragon::Load()
    {
        try
        {
            MonsterInfo *info = getEnvir()->GetMonsterInfo(Info->MonsterName);
            if (info == nullptr)
            {
                getMessageQueue()->Enqueue("Failed to load Dragon (bad monster name): " + Info->MonsterName);
                return false;
            }
            LinkedMonster = MonsterObject::GetMonster(info);

            Map *map = getEnvir()->GetMapByNameAndInstance(Info->MapFileName);
            if (map == nullptr)
            {
                getMessageQueue()->Enqueue("Failed to load Dragon (bad map name): " + Info->MapFileName);
                return false;
            }

            if (Info->Location->X > map->Width || Info->Location->Y > map->Height)
            {
                getMessageQueue()->Enqueue("Failed to load Dragon (bad map XY): " + Info->MapFileName);
                return false;
            }

            if (LinkedMonster->Spawn(map, Info->Location))
            {
                EvilMir *mob = dynamic_cast<EvilMir*>(LinkedMonster);
                if (mob != nullptr)
                {
                    if (mob != nullptr)
                    {
                        mob->setDragonLink(true);
                    }
                }
                MonsterInfo *bodyinfo = getEnvir()->GetMonsterInfo(Info->BodyName);
                if (bodyinfo != nullptr)
                {
                    MonsterObject *bodymob;
                    Point *spawnlocation = Point::Empty;
                    for (int i = 0; i <= BodyLocations.size() - 1; i++)
                    {
                        bodymob = MonsterObject::GetMonster(bodyinfo);
                        spawnlocation = new Point(LinkedMonster->getCurrentLocation()->X + BodyLocations[i]->X, LinkedMonster->getCurrentLocation()->Y + BodyLocations[i]->Y);
                        if (bodymob != nullptr)
                        {
                            bodymob->Spawn(LinkedMonster->getCurrentMap(), spawnlocation);
                        }
                    }

//C# TO C++ CONVERTER TODO TASK: A 'delete spawnlocation' statement was not added since spawnlocation was passed to a method or constructor. Handle memory management manually.
                }

                DropArea = new Rectangle(Info->DropAreaTop->X, Info->DropAreaTop->Y, Info->DropAreaBottom->X - Info->DropAreaTop->X, Info->DropAreaBottom->Y - Info->DropAreaTop->Y);
                Loaded = true;
                return true;
            }
        }
        catch (const std::runtime_error &ex)
        {
            getMessageQueue()->Enqueue(ex);
        }

        getMessageQueue()->Enqueue("Failed to load Dragon");
        return false;
    }

    void Dragon::GainExp(int ammount)
    {
        if (ammount <= 0)
        {
            return;
        }

        Info->Experience += ammount;
        if (Info->Experience >= Info->Exps[std::min(11, Info->Level - 1)])
        {
            Info->Experience -= Info->Exps[std::min(11, Info->Level - 1)];
            LevelUp();
        }
    }

    void Dragon::LevelUp()
    {
        Drop(Info->Level); //i would suggest having the max level drop be empty or 'trash' > that way you stop ppl from exploiting it
        if (Info->Level < Globals::MaxDragonLevel)
        {
            Info->Level = static_cast<unsigned char>(std::max(1, (Info->Level + 1)));
        }
        //if it reaches max level > make it stay that level for 6*deleveldelay and then reset to 0, rather then letting ppl farm it by making it drop every hour
        if (Info->Level == Globals::MaxDragonLevel)
        {
            DeLevelTime = getEnvir()->getTime() + (6 * DeLevelDelay);
        }
    }

    void Dragon::LevelDown()
    {
        if (Info->Level > 1)
        {
            Info->Level = static_cast<unsigned char>(std::max(1, (Info->Level - 1)));
            Info->Experience = 0;
        }
    }

    void Dragon::Drop(unsigned char level)
    {
        if (level > Info->Drops.size())
        {
            return;
        }
        if (Info->Drops[level - 1].empty())
        {
            return;
        }
        if (LinkedMonster == nullptr)
        {
            return;
        }
        std::vector<DragonInfo::DropInfo*> droplist(Info->Drops[level - 1]);

        for (int i = 0; i < droplist.size(); i++)
        {
            DragonInfo::DropInfo *drop = droplist[i];

            int rate = static_cast<int>(drop->Chance / Settings::DropRate);
            if (rate < 1)
            {
                rate = 1;
            }
            if (getEnvir()->Random->Next(rate) != 0)
            {
                continue;
            }

            if (drop->Gold > 0)
            {
                int gold = getEnvir()->Random->Next(static_cast<int>(drop->Gold / 2), static_cast<int>(drop->Gold + drop->Gold / 2)); //Messy

                if (gold <= 0)
                {
                    continue;
                }

                if (!DropGold(static_cast<unsigned int>(gold)))
                {
                    return;
                }
            }
            else
            {
                UserItem *item = getEnvir()->CreateDropItem(drop->Item);
                if (item == nullptr)
                {
                    continue;
                }
                if (!DropItem(item))
                {
                    return;
                }
            }
        }
    }

    bool Dragon::DropItem(UserItem *item)
    {
        Point *droplocation = new Point(DropArea->Left + (DropArea->Width / 2), DropArea->Top);
        ItemObject *ob = new ItemObject(this->LinkedMonster, item, droplocation);
        ob->Owner = this->LinkedMonster->EXPOwner;
        ob->OwnerTime = getEnvir()->getTime() + Settings::Minute;

        delete ob;
//C# TO C++ CONVERTER TODO TASK: A 'delete droplocation' statement was not added since droplocation was passed to a method or constructor. Handle memory management manually.
        return ob->DragonDrop(DropArea->Width / 2);
    }

    bool Dragon::DropGold(unsigned int gold)
    {
        if (this->LinkedMonster->EXPOwner != nullptr && this->LinkedMonster->EXPOwner->CanGainGold(gold))
        {
            this->LinkedMonster->EXPOwner->WinGold(gold);
            return true;
        }

        Point *droplocation = new Point(DropArea->Left + (DropArea->Width / 2), DropArea->Top);
        ItemObject *ob = new ItemObject(this->LinkedMonster, gold, droplocation);
        ob->Owner = this->LinkedMonster->EXPOwner;
        ob->OwnerTime = getEnvir()->getTime() + Settings::Minute;

        delete ob;
//C# TO C++ CONVERTER TODO TASK: A 'delete droplocation' statement was not added since droplocation was passed to a method or constructor. Handle memory management manually.
        return ob->DragonDrop(DropArea->Width / 2);
    }

    void Dragon::Process()
    {
        if (!Loaded)
        {
            return;
        }
        if (getEnvir()->getTime() < ProcessTime)
        {
            return;
        }

        ProcessTime = getEnvir()->getTime() + ProcessDelay;

        if ((Info->Level >= Globals::MaxDragonLevel) && (getEnvir()->getTime() > DeLevelTime))
        {
            Info->Level = static_cast<unsigned char>(1);
            Info->Experience = 0;
            DeLevelTime = getEnvir()->getTime() + DeLevelDelay;
        }

        if (Info->Level > 1 && getEnvir()->getTime() > DeLevelTime)
        {
            LevelDown();
            DeLevelTime = getEnvir()->getTime() + DeLevelDelay;
        }
    }
}
